﻿/* Copyright (c) Microsoft Corporation
 * 
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * 
 * THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
 * 
 * See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ProximityTapper
{
    /// <summary>
    /// Class for ExtensionSubMessage
    /// </summary>
    public class ExtensionSubMessage : IExtensionSubMessage
    {
        private static UInt32 MAX_TYPE = 8;
        private static UInt32 EXTENSION_SUB_MESSAGE_MIN_SIZE = MAX_TYPE + sizeof(byte);

        // byte array to store extension type
        private byte[] extensionType = new byte[MAX_TYPE];

        public byte[] ExtensionType
        {
            get { return extensionType; }
            set { extensionType = value; }
        }

        // byte array containing extension data
        public byte[] ExtensionData { get; private set; }

        /// <summary>
        /// Try parsing extension sub message data
        /// </summary>
        /// <param name="data">Message Data</param>
        /// <param name="offset">
        /// Starting / current position in byte array.
        /// This is updated as the bytes are read.
        /// </param>
        /// <returns>true is successful in parsing, false otherwise</returns>
        /// <summary>
        bool IExtensionSubMessage.TryParse(byte[] data, ref UInt32 offset)
        {
            bool isParsed = true;

            try
            {
                // Verify that remaining data length is atleast minimum size 
                // required by Service Descriptor Entry Header
                if (EXTENSION_SUB_MESSAGE_MIN_SIZE <= (data.Length - offset))
                {
                    // Copy extension type
                    Array.Copy(data, offset, this.ExtensionType, 0, MAX_TYPE);
                    offset += MAX_TYPE;

                    // Get data size
                    byte extensionDataSize = data[offset];
                    offset++;

                    // Parse further if data is available
                    if (extensionDataSize > 0)
                    {
                        // Confirm that we have enough bytes to read extended payload
                        if (extensionDataSize <= (data.Length - offset))
                        {
                            // Copy extension data
                            this.ExtensionData = new byte[extensionDataSize];
                            Array.Copy(data, offset, this.ExtensionData, 0, extensionDataSize);
                            offset += extensionDataSize;
                        }
                        else
                        {
                            // Parsing error
                            isParsed = false;
                        }
                    }
                }
                else
                {
                    // Parsing error
                    isParsed = false;
                }
            }
            catch (Exception)
            {
                // Parsing error
                isParsed = false;
            }

            return isParsed;
        }

        public override string ToString()
        {
            String output = "\n\nExtension Sub Message";

            output += "\nExtension Type : " + StringFormatter.ToHexString(this.ExtensionType);

            if (this.ExtensionData != null)
            {
                output += "\nExtension Data Size : " + StringFormatter.ToHexString(this.ExtensionData.Length);
                output += "\nExtension Data : " + StringFormatter.ToHexString(this.ExtensionData);
            }
            else
            {
                output += "\nExtension Data Size : 0x0";
            }

            return output;
        }
    }

    /// <summary>
    /// Class for SessionActivation / SessionActivationAck message
    /// This class can store either SessionActivation or SessionActivationAck message 
    /// depending on the value of IsAck passed during constructor. 
    /// It would also try to parse messages differently.
    /// </summary>
    [CLSCompliant(false)]
    public class SessionActivation : ISessionActivation, IMessageData
    {
        private static UInt32 MAX_ID = 8;
        private static UInt32 MAX_ECDH_KEY = 32;
        private static UInt32 SESSION_ACTIVATION_MIN_SIZE = 3 * MAX_ID + 2 * sizeof(UInt32) + 2 * MAX_ECDH_KEY;
        private static UInt32 SESSION_ACTIVATION_ACK_MIN_SIZE = 2 * sizeof(UInt32) + 2 * MAX_ECDH_KEY + sizeof(UInt16) + sizeof(byte);

        // This determines if current message is Session Activation message
        // or Session Activation Ack Message
        public bool IsAck { get; private set; }

        // byte array storing Source ID
        // Used in Session Activation Message (8 bytes)
        private byte[] sourceId = new byte[MAX_ID];

        public byte[] SourceId
        {
            get { return sourceId; }
            set { sourceId = value; }
        }

        // byte array storing Activated Session Factory ID
        // Used in Session Activation Message (8 bytes)
        private byte[] activatedSessionFactoryId = new byte[MAX_ID];

        public byte[] ActivatedSessionFactoryId
        {
            get { return activatedSessionFactoryId; }
            set { activatedSessionFactoryId = value; }
        }

        // byte array storing Reply Channel ID
        // Used in Session Activation Message (8 bytes)
        private byte[] replyChannelId = new byte[MAX_ID];

        public byte[] ReplyChannelId
        {
            get { return replyChannelId; }
            set { replyChannelId = value; }
        }

        // Public Key Magic Number
        // Used in Session Activation Message (32 bits)
        // Also used in Session Activation Message Ack (32 bits)
        public UInt32 ECDHPublicKeyMagicNumber { get; private set; }

        // Public key length
        // Used in Session Activation Message (32 bits)
        // Also used in Session Activation Message Ack (32 bits)
        public UInt32 ECDHPublicKeyLength { get; private set; }

        // byte array containing X param for the ECDH key
        // Used in Session Activation Message (32 bytes)
        // Also used in Session Activation Message Ack (32 bytes)
        private byte[] ecdh_X = new byte[MAX_ECDH_KEY];

        public byte[] ECDH_X
        {
            get { return ecdh_X; }
            set { ecdh_X = value; }
        }

        // byte array containing Y param for the ECDH key
        // Used in Session Activation Message (32 bytes)
        // Also used in Session Activation Message Ack (32 bytes)
        private byte[] ecdh_Y = new byte[MAX_ECDH_KEY];

        public byte[] ECDH_Y
        {
            get { return ecdh_Y; }
            set { ecdh_Y = value; }
        }

        // TCP Port
        // Used in Session Activation Message Ack (16 bits)
        public UInt16 TCPPort { get; private set; }

        // RF Comm Port
        // Used in Session Activation Message Ack (8 bits)
        public byte RFCOMMPort { get; private set; }

        // Reserved word (optional)
        // Used in Session Activation Message (32 bits)
        // Also used in Session Activation Message Ack (8 bits)
        public UInt32 Reserved1 { get; private set; }

        // Reserved word 2 (optional)
        // Used in Session Activation Message (32 bits)
        // Also used in Session Activation Message Ack (32 bits)
        public UInt32 Reserved2 { get; private set; }

        // Reserved word 3 (optional)
        // Used in Session Activation Message (16 bits)
        // Also used in Session Activation Message Ack (32 bits)
        public UInt32 Reserved3 { get; private set; }

        // Reserved word 4 (optional)
        // Used in Session Activation Message Ack (16 bits)
        public UInt32 Reserved4 { get; private set; }

        // List containing extension sub message (optional)
        // Used in Session Activation Message 
        // Also used in Session Activation Message Ack
        private List<IExtensionSubMessage> extensionList = new List<IExtensionSubMessage>();

        public List<IExtensionSubMessage> ExtensionList
        {
            get { return extensionList; }
            set { extensionList = value; }
        }

        /// <summary>
        /// Constructor. 
        /// This takes in a bool value that determines if this should act as
        /// Session Activation message or Session Actvation Ack message
        /// </summary>
        /// <param name="isAck">Is Ack message or not</param>
        public SessionActivation(bool isAck)
        {
            this.IsAck = isAck;
        }

        /// <summary>
        /// Function to try parse SessionActivation message data
        /// </summary>
        /// <param name="data">Message Data</param>
        /// <returns>true is successful in parsing, false otherwise</returns>
        bool IMessageData.TryParse(byte[] data)
        {
            bool isParsed = true;
            UInt32 offset = 0;

            // Calculate min size for Session Activation
            UInt32 minSize = (this.IsAck) ? SESSION_ACTIVATION_ACK_MIN_SIZE : SESSION_ACTIVATION_MIN_SIZE;

            try
            {
                // Verify that remaining data length is atleast minimum size 
                // required by SessionActivation message
                if (minSize <= (data.Length - offset))
                {
                    // Check if this is ServiceActivationAck
                    if (!this.IsAck)
                    {
                        Array.Copy(data, offset, this.SourceId, 0, MAX_ID);
                        offset += MAX_ID;

                        Array.Copy(data, offset, this.ActivatedSessionFactoryId, 0, MAX_ID);
                        offset += MAX_ID;

                        Array.Copy(data, offset, this.ReplyChannelId, 0, MAX_ID);
                        offset += MAX_ID;
                    }

                    this.ECDHPublicKeyMagicNumber = BigEndianConverter.ToUInt32(data, offset);
                    offset += sizeof(UInt32);

                    this.ECDHPublicKeyLength = BitConverter.ToUInt32(data, (int)offset);
                    offset += sizeof(UInt32);

                    Array.Copy(data, offset, this.ECDH_X, 0, MAX_ECDH_KEY);
                    offset += MAX_ECDH_KEY;

                    Array.Copy(data, offset, this.ECDH_Y, 0, MAX_ECDH_KEY);
                    offset += MAX_ECDH_KEY;

                    if (this.IsAck)
                    {
                        this.TCPPort = BigEndianConverter.ToUInt16(data, offset);
                        offset += sizeof(UInt16);

                        this.RFCOMMPort = data[offset];
                        offset++;
                    }

                    if (this.IsAck)
                    {
                        if (sizeof(byte) <= (data.Length - offset))
                        {
                            this.Reserved1 = data[offset];
                            offset++;
                        }
                    }
                    else
                    {
                        if (sizeof(UInt32) <= (data.Length - offset))
                        {
                            this.Reserved1 = BigEndianConverter.ToUInt32(data, offset);
                            offset += sizeof(UInt32);
                        }
                    }

                    if (sizeof(UInt32) <= (data.Length - offset))
                    {
                        this.Reserved2 = BigEndianConverter.ToUInt32(data, offset);
                        offset += sizeof(UInt32);
                    }

                    if (this.IsAck)
                    {
                        if (sizeof(UInt32) <= (data.Length - offset))
                        {
                            this.Reserved3 = BigEndianConverter.ToUInt32(data, offset);
                            offset += sizeof(UInt32);
                        }

                        if (sizeof(UInt16) <= (data.Length - offset))
                        {
                            this.Reserved4 = BigEndianConverter.ToUInt16(data, offset);
                            offset += sizeof(UInt16);
                        }
                    }
                    else
                    {
                        if (sizeof(UInt16) <= (data.Length - offset))
                        {
                            this.Reserved3 = BigEndianConverter.ToUInt16(data, offset);
                            offset += sizeof(UInt16);
                        }
                    }

                    UInt16 extensionSubMessageCount = 0;

                    if (sizeof(UInt16) <= (data.Length - offset))
                    {
                        extensionSubMessageCount = BigEndianConverter.ToUInt16(data, offset);
                        offset += sizeof(UInt16);
                    }

                    for (; extensionSubMessageCount > 0; extensionSubMessageCount--)
                    {
                        IExtensionSubMessage iExtensionSubMessage = new ExtensionSubMessage();

                        if (!iExtensionSubMessage.TryParse(data, ref offset))
                        {
                            // Parsing error
                            isParsed = false;
                            break;
                        }

                        this.ExtensionList.Add(iExtensionSubMessage);
                    }

                }
                else
                {
                    // Parsing error
                    isParsed = false;
                }
            }
            catch (Exception)
            {
                // Parsing error
                isParsed = false;
            }

            // Make sure that all bytes are read
            if (offset != data.Length)
            {
                isParsed = false;
            }

            return isParsed;
        }

        public override string ToString()
        {
            String output = "";

            if (!this.IsAck)
            {
                output += "Message Type : SessionActivation";
                output += "\nSource ID : " + Convert.ToBase64String(this.SourceId);
                output += "\nActivated Session Factory Id : " + Convert.ToBase64String(this.ActivatedSessionFactoryId);
                output += "\nReply Channel Id : " + Convert.ToBase64String(this.ReplyChannelId);
            }
            else
            {
                output += "Message Type : SessionActivationAck";
            }

            output += "\nECDH Public Key Magic Number : " + StringFormatter.ToHexString(this.ECDHPublicKeyMagicNumber);

            output += "\nECDH Public Key Length : " + StringFormatter.ToHexString(this.ECDHPublicKeyLength);

            output += "\nECDH XParam : " + StringFormatter.ToHexString(this.ECDH_X);
            output += "\nECDH YParam : " + StringFormatter.ToHexString(this.ECDH_Y);

            if (this.IsAck)
            {
                output += "\nTCP Port : " + StringFormatter.ToHexString(this.TCPPort);
                output += "\nRFCOMM Port : " + StringFormatter.ToHexString(this.RFCOMMPort);
            }

            if (this.Reserved1 > 0x0)
            {
                output += "\nReserved 1 : " + StringFormatter.ToHexString(this.Reserved1);
            }

            if (this.Reserved2 > 0x0)
            {
                output += "\nReserved 2 : " + StringFormatter.ToHexString(this.Reserved2);
            }

            if (this.Reserved3 > 0x0)
            {
                output += "\nReserved 3 : " + StringFormatter.ToHexString(this.Reserved3);
            }

            if (this.Reserved4 > 0x0)
            {
                output += "\nReserved 4 : " + StringFormatter.ToHexString(this.Reserved4);
            }

            output += "\nExtension Sub Message Count : " + StringFormatter.ToHexString(this.ExtensionList.Count);

            foreach (IExtensionSubMessage extensionSubMessage in ExtensionList)
            {
                output += extensionSubMessage.ToString();
            }

            return output;
        }
    }
}
